import json
import logging
import typing
from difflib import SequenceMatcher
from typing import List, Text, Tuple

import rasa.cli.utils
import rasa.utils.io
from rasa.cli import utils as cli_utils
from rasa.core.actions.action import ACTION_LISTEN_NAME
from rasa.core.channels import console
from rasa.core.channels.channel import CollectingOutputChannel, UserMessage
from rasa.core.domain import Domain
from rasa.core.events import ActionExecuted, UserUttered
from rasa.core.trackers import DialogueStateTracker
from rasa.utils.common import raise_warning

if typing.TYPE_CHECKING:
    from rasa.core.agent import Agent

logger = logging.getLogger()  # get the root logger


def _check_prediction_aligns_with_story(
    last_prediction: List[Text], actions_between_utterances: List[Text]
) -> None:
    """如果预测与预期的行动不符，则发出警告。"""

    p, a = align_lists(last_prediction, actions_between_utterances)
    if p != a:
        raise_warning(
            f"The model predicted different actions than the "
            f"model used to create the story! Expected: "
            f"{p} but got {a}."
        )


def align_lists(
    predictions: List[Text], golds: List[Text]
) -> Tuple[List[Text], List[Text]]:
    """对齐两个列表，尝试将相同的元素保持在相同的索引处。

    如果列表在某些索引处包含不同的项目，则算法将尝试找到最佳对齐方式，并在必要时填充“None”值。"""

    padded_predictions = []
    padded_golds = []
    s = SequenceMatcher(None, predictions, golds)

    for tag, i1, i2, j1, j2 in s.get_opcodes():
        padded_predictions.extend(predictions[i1:i2])
        padded_predictions.extend(["None"] * ((j2 - j1) - (i2 - i1)))

        padded_golds.extend(golds[j1:j2])
        padded_golds.extend(["None"] * ((i2 - i1) - (j2 - j1)))

    return padded_predictions, padded_golds


def actions_since_last_utterance(tracker: DialogueStateTracker) -> List[Text]:
    """在用户最近一次发声之后提取所有事件。"""

    actions = []
    for e in reversed(tracker.events):
        if isinstance(e, UserUttered):
            break
        elif isinstance(e, ActionExecuted):
            actions.append(e.action_name)
    actions.reverse()
    return actions


async def replay_events(tracker: DialogueStateTracker, agent: "Agent") -> None:
    """使用一个跟踪器，回放记录的用户对代理的话语。

    在回放用户语句的过程中，将代理创建的已执行操作和事件与正在回放的跟踪器的已记录动作和事件进行比较。
    如果它们不一致，则会记录警告。

    最后，存储在同一发送方id的代理的跟踪器存储中的跟踪器将与重放的跟踪器具有完全相同的状态。"""

    actions_between_utterances = []
    last_prediction = [ACTION_LISTEN_NAME]

    for i, event in enumerate(tracker.events_after_latest_restart()):
        if isinstance(event, UserUttered):
            _check_prediction_aligns_with_story(
                last_prediction, actions_between_utterances
            )

            actions_between_utterances = []
            cli_utils.print_success(event.text)
            out = CollectingOutputChannel()
            await agent.handle_text(
                event.text, sender_id=tracker.sender_id, output_channel=out
            )
            for m in out.messages:
                buttons = m.pop("buttons", None)  # for non-terminal stdin
                console.print_bot_output(m)

                if buttons is not None:
                    color = rasa.cli.utils.bcolors.OKBLUE
                    rasa.cli.utils.print_color("Buttons:", color=color)
                    for idx, button in enumerate(buttons):
                        rasa.cli.utils.print_color(
                            cli_utils.button_to_string(button, idx), color=color
                        )

            tracker = agent.tracker_store.retrieve(tracker.sender_id)
            last_prediction = actions_since_last_utterance(tracker)

        elif isinstance(event, ActionExecuted):
            actions_between_utterances.append(event.action_name)

    _check_prediction_aligns_with_story(last_prediction, actions_between_utterances)


def load_tracker_from_json(tracker_dump: Text, domain: Domain) -> DialogueStateTracker:
    """从文件中读取json转储并实例化一个tracker。"""

    tracker_json = json.loads(rasa.utils.io.read_file(tracker_dump))
    sender_id = tracker_json.get("sender_id", UserMessage.DEFAULT_SENDER_ID)
    return DialogueStateTracker.from_dict(
        sender_id, tracker_json.get("events", []), domain.slots
    )
